/* server.c */

/************************************************************************************
*                    Copyright Aquantia Corporation
*                              Freeware
*
* Description:
*
*   This file contains the TCP server start function
*
************************************************************************************/

/*! \file
    This file contains the TCP server start function
*/

#include "server.h"
#include "tcpServer.h"

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#ifdef __cplusplus
  extern "C" {
#endif


struct ip_mreq
{
	struct in_addr imr_multiaddr;
	struct in_addr imr_interface;
};

/* When set, this dumps additional debug information to the FILE handle VerboseServerOutput */
unsigned int TCP_VERBOSE = 0;

/* The FILE handle where debugging information is written to.  This initializes to stdout. */
FILE* TCP_VerboseServerOutput;

/* The FILE handle where error information is written to.  This initializes to stderr. */
FILE* TCP_ErrorServerOutput;

/* Function perfoms actions on server stop */
static int stopServer(int retCode)
{
  sockClose();
  return retCode;
}

/* Function sets socket options and returns non-zero value in case of error. */
int setKeepAliveOptions(SOCKET clientSocket)
{
#ifdef TCP_KEEP_ALIVE
  int keepAliveTrue = 1;
  int keepAliveTime = 0;
  int keepAliveTimeSpecified = 0;
  int keepAliveInterval = 0;
  int keepAliveIntervalSpecified = 0;
  int keepAliveCount = 0;
  int keepAliveCountSpecified = 0;
  
  int optionValue;
  socklen_t optionValueLength = sizeof(optionValue);
  char verbose[509] = { 0 };
  
  #ifdef TCP_KEEP_ALIVE_TIME
    keepAliveTime = TCP_KEEP_ALIVE_TIME;
    keepAliveTimeSpecified = 1;
  #endif
  #ifdef TCP_KEEP_ALIVE_INTERVAL
    keepAliveInterval = TCP_KEEP_ALIVE_INTERVAL;
    keepAliveIntervalSpecified = 1;
  #endif
  #ifdef TCP_KEEP_ALIVE_COUNT
    keepAliveCount = TCP_KEEP_ALIVE_COUNT;
    keepAliveCountSpecified = 1;
  #endif

  /* Turn on socket keep alive, and set the appropriate TCP parameters */
  if (setsockopt (clientSocket, SOL_SOCKET, SO_KEEPALIVE, (const void*) &keepAliveTrue, sizeof (keepAliveTrue)))
  {
    fprintf (TCP_ErrorServerOutput, "Error turning on socket keep-alive function: %s", get_socket_error());
    return TCP_SERVER_E_SOCKET_SET_OPT;
  }
  if (setTcpKeepAliveOptions(clientSocket, 
                             keepAliveTime, keepAliveTimeSpecified, 
                             keepAliveInterval, keepAliveIntervalSpecified,
                             keepAliveCount, keepAliveCountSpecified, verbose))
  {
    fprintf (TCP_ErrorServerOutput, "Error setting TCP keep-alive options: %s", get_socket_error());
    return TCP_SERVER_E_SOCKET_SET_OPT;
  }
  if ((TCP_VERBOSE) && strlen(verbose))
    fprintf (TCP_VerboseServerOutput, verbose);
  
  if (TCP_VERBOSE)
  {
    /* Verify the TCP timeout settings */
    if (getsockopt (clientSocket, SOL_SOCKET, SO_KEEPALIVE, (void*) &optionValue, &optionValueLength) < 0)
    {
      fprintf (TCP_ErrorServerOutput, "Error reading SO_KEEPALIVE on new socket: %s", get_socket_error());
      return TCP_SERVER_E_SOCKET_GET_OPT;
    }
    fprintf(TCP_VerboseServerOutput, "SO_KEEPALIVE is %s\n", (optionValue ? "ON" : "OFF"));

    if (getTcpKeepAliveOptions(clientSocket, 
                               &keepAliveTime, &keepAliveTimeSpecified, 
                               &keepAliveInterval, &keepAliveIntervalSpecified,
                               &keepAliveCount, &keepAliveCountSpecified))
    {
      fprintf (TCP_ErrorServerOutput, "Error reading TCP keep-alive options: %s", get_socket_error());
      return TCP_SERVER_E_SOCKET_GET_OPT;
    }
    if (keepAliveTimeSpecified)
      fprintf(TCP_VerboseServerOutput, "SO_KEEPALIVE time is %is\n", keepAliveTime);
    else
      fprintf(TCP_VerboseServerOutput, "SO_KEEPALIVE time can't be read\n");
    if (keepAliveIntervalSpecified)
      fprintf(TCP_VerboseServerOutput, "TCP_KEEPINTVL is %is\n", keepAliveInterval);
    else
      fprintf(TCP_VerboseServerOutput, "TCP_KEEPINTVL can't be read\n");
    if (keepAliveCountSpecified)
      fprintf(TCP_VerboseServerOutput, "TCP_KEEPCNT is %i attempts\n", keepAliveCount);
    else
      fprintf(TCP_VerboseServerOutput, "TCP_KEEPCNT can't be read\n");
  }
#endif
  return 0;
}

/*! \addtogroup Server Server
This module contains the functions and data structures for the server and the main() program.
@{*/

/* The main function for the TCP server.  This is designed to be called as a daemon,
    handles both TCP relay to the local hwInterface as well as discovery. 
    This function also sets the output system of server execution. 
    Returns non-zero value on failure. */
int runServer(unsigned int verbose, FILE* verboseOutput, FILE* errorOutput)
{
  int reuseAddressEnable = 1;
  SOCKET udpSocket;
  SOCKET masterSocket, newClientSocket;
  TCP_Socket_List tcpSocketList;
  char *ipAddressPointer, *hostNamePointer;
  int activity, i, j, bytesRead, TxBufferSize;
  SOCKET socketDescriptor = 0;
  SOCKET largestSocketDescriptor = 0;
  struct sockaddr_in address;
  struct ip_mreq group;
  socklen_t addressLength;
  char hostName[HOST_NAME_SIZE] = { 0 };

  /* data buffers for maximum standard size Ethernet frame */
  uint8_t RxBuffer[BUFFER_SIZE];
  uint8_t TxBuffer[BUFFER_SIZE];

  fd_set socketDescriptors;          /* set of socket descriptors */

  TCP_VERBOSE = verbose;
  TCP_VerboseServerOutput = verboseOutput;
  TCP_ErrorServerOutput = errorOutput;

  /**************************************************************************************/
  /* initialize the TCP socket list                                                     */
  /**************************************************************************************/
  tcpSocketList.numberOfClients = 0;
  for (i = 0; i < MAX_NUMBER_OF_CLIENTS; i++)
  {
    tcpSocketList.socketID[i] = 0;
    tcpSocketList.hostName[i] = (char*) malloc ((HOST_NAME_SIZE + 1) * sizeof (char));
    tcpSocketList.ipAddress[i] = (char*) malloc (22 * sizeof (char));                     /*"aaa.bbb.ccc.ddd:nnnnn0" = length 22*/
  }

  /**************************************************************************************/
  /* initialize socket library                                                          */
  /**************************************************************************************/
  if (sockInitialize() != 0)
  {
    fprintf (TCP_ErrorServerOutput, "Socket library initialize failed: %s\n", get_socket_error());
    return stopServer(TCP_SERVER_E_SOCKET_INIT);
  }
  /**************************************************************************************/
  /* create a master TCP over IPv4 socket that will be used for each connection request */
  /**************************************************************************************/
  if ((masterSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
  {
    fprintf (TCP_ErrorServerOutput, "Unable to create master socket: %s\n", get_socket_error());
    return stopServer(TCP_SERVER_E_SOCKET_CREATE);
  }

  /* Set master socket to allow the socket to be reused during TIME_WAIT state */
  if (setsockopt (masterSocket, SOL_SOCKET, SO_REUSEADDR, (const void*) &reuseAddressEnable, sizeof (reuseAddressEnable)) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Set socket option to SO_REUSEADDR failed: %s\n", get_socket_error());
    CLOSE (masterSocket);
    return stopServer(TCP_SERVER_E_SOCKET_SET_OPT);
  }

  /* initialize the address structure to bind to the TCP socket, using the default address */
  addressLength = sizeof (address);
  memset ((void*) &address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons (TCP_SERVER_PORT);

  /* bind the socket to TCP_SERVER_PORT */
  if (bind (masterSocket, (struct sockaddr*) &address, sizeof (address)) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Bind of master socket to port %i failed: %s\n", TCP_SERVER_PORT, get_socket_error());
    CLOSE (masterSocket);
    return stopServer(TCP_SERVER_E_SOCKET_BIND);
  }

  /* listen on the master socket with a maximum of 3 pending connections */
  if (listen (masterSocket, 3) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Error on listen() on masterSocket: %s\n", get_socket_error());
    CLOSE (masterSocket);
    return stopServer(TCP_SERVER_E_SOCKET_LISTEN);
  }
  largestSocketDescriptor = masterSocket;  /* keep track of the largest socket descriptor */

  if (TCP_VERBOSE)
    fprintf (TCP_VerboseServerOutput, "TCP listener set up on port %i \n", TCP_SERVER_PORT);

  /**************************************************************************************/
  /* create a UDP over IPv4 socket that will be used for server discovery               */
  /**************************************************************************************/
  /* create the UDP over IPv4 socket */
  if ((udpSocket = socket (AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
  {
    fprintf (TCP_ErrorServerOutput, "Unable to create UDP socket: %s\n", get_socket_error());
    CLOSE (masterSocket);
    return stopServer(TCP_SERVER_E_SOCKET_CREATE);
  }

  /* Set master socket to allow the socket to be reused during TIME_WAIT state */
  if (setsockopt (udpSocket, SOL_SOCKET, SO_REUSEADDR, (const void*) &reuseAddressEnable, sizeof (reuseAddressEnable)) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Set socket option to SO_REUSEADDR failed: %s\n", get_socket_error());
    CLOSE (masterSocket);
    CLOSE (udpSocket);
    return stopServer(TCP_SERVER_E_SOCKET_SET_OPT);
  }

  /* bind the UDP discovery socket to the UDP_DISCOVERY_SERVER_PORT port, as otherwise the OS randomly assigns one */
  address.sin_port = htons (UDP_DISCOVERY_SERVER_PORT);
  if (bind (udpSocket, (struct sockaddr*) &address, sizeof (address)) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Bind of UDP socket to port %i failed: %s\n", UDP_DISCOVERY_SERVER_PORT, get_socket_error());
    CLOSE (masterSocket);
    CLOSE (udpSocket);
    return stopServer(TCP_SERVER_E_SOCKET_BIND);
  }


  group.imr_multiaddr.s_addr = inet_addr (UDP_DISCOVERY_SERVER_MULTICAST_ADDRESS);
  group.imr_interface.s_addr = htonl (INADDR_ANY);
  if (setsockopt (udpSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &group, sizeof (group)) < 0)
  {
    fprintf (TCP_ErrorServerOutput, "Unable to join multicast group %s: %s\n", UDP_DISCOVERY_SERVER_MULTICAST_ADDRESS, get_socket_error());
    CLOSE (masterSocket);
    CLOSE (udpSocket);
    return stopServer(TCP_SERVER_E_SOCKET_SET_OPT);
  }

  /* load the host name so that we can return this as part of the discovery protocol */
  if (get_host_name(hostName, HOST_NAME_SIZE) != 0)
  {
    fprintf (TCP_ErrorServerOutput, "Unable to get host name: %s\n", get_socket_error());
    CLOSE (masterSocket);
    CLOSE (udpSocket);
    return stopServer(TCP_SERVER_E_GET_HOST_NAME);
  }

  /* keep track of the largest socket descriptor */
  if (udpSocket > largestSocketDescriptor) largestSocketDescriptor = udpSocket;

  if (TCP_VERBOSE)
    fprintf (TCP_VerboseServerOutput, "UPD socket set up on port %i on %s\n", UDP_DISCOVERY_SERVER_PORT, hostName);

  /**************************************************************************************/
  /* select() server polling loop                                                       */
  /**************************************************************************************/
  tcpSocketList.numberOfClients = 0;
  while (1)
  {
    /* clear the set of socket descriptors - every poll, we must re-add the sockets to the list - which is stored as a bit array */
    FD_ZERO (&socketDescriptors);

    /* (re)add UDP socket to set */
    FD_SET (udpSocket, &socketDescriptors);

    /* (re)add TCP master socket to set */
    FD_SET (masterSocket, &socketDescriptors);

    /* add all of the active TCP client sockets to the set */
    for (i = 0; i < tcpSocketList.numberOfClients; i++)
      FD_SET (tcpSocketList.socketID[i], &socketDescriptors);

    /* wait for incoming (aka read) activity on one of the sockets: timeout is NULL, so wait indefinitely */
    activity = select (largestSocketDescriptor + 1, &socketDescriptors, NULL, NULL, NULL);

    if ((activity < 0) && (!is_socket_error_equal(EINTR)))
      fprintf (TCP_ErrorServerOutput, "Error on select(): %s\n", get_socket_error());

    /*--------------------------------------------------*/
    /* Check for discovery requests on the UDP socket   */
    /*--------------------------------------------------*/
    if (FD_ISSET (udpSocket, &socketDescriptors))
    {
      if ((bytesRead = recvfrom (udpSocket, (char*) RxBuffer, BUFFER_SIZE, 0, (struct sockaddr*) &address, &addressLength)) > 0)
      {
        /* incoming discovery request */
        if (TCP_VERBOSE)
        {
          fprintf (TCP_VerboseServerOutput, "%i byte message from IP address %s:%i:\n", bytesRead, inet_ntoa (address.sin_addr), ntohs (address.sin_port));
          for (i = 0; i < bytesRead; i++) fprintf (TCP_VerboseServerOutput, "%.2X ", RxBuffer[i]);
          fprintf (TCP_VerboseServerOutput, "\n");
          for (i = 0; i < bytesRead; i++)
          {
            if (RxBuffer[i] >= 0x20) fprintf (TCP_VerboseServerOutput, "%c  ", RxBuffer[i]);
            else fprintf (TCP_VerboseServerOutput, "   ");
          }
          fprintf (TCP_VerboseServerOutput, "\n");
        }  /* if (TCP_VERBOSE) */

        /* Verify that this is a legitimate message */
        if (strncmp ((char*) RxBuffer, BROADCAST_MESSAGE, bytesRead) == 0)
        {
          /* respond with the server's hostname: length is strlen (hostName) + 1 for NULL terminator */
          if (TCP_VERBOSE)
            fprintf (TCP_VerboseServerOutput, "Received valid UDP discovery message: %s\n", BROADCAST_MESSAGE);

          if (sendto (udpSocket, hostName, strlen (hostName) + 1, 0, (const struct sockaddr*) &address, addressLength) < 0)
            fprintf (TCP_ErrorServerOutput, "Error writing data to IP address %s:%i - %s\n", inet_ntoa (address.sin_addr), ntohs (address.sin_port), get_socket_error());
        }
        else
        {
          /* respond with the server's hostname */
          if (TCP_VERBOSE)
            fprintf (TCP_VerboseServerOutput, "Ignoring invalid message on UDP discovery port\n");
        }  /* if (strncmp ((char*) RxBuffer, BROADCAST_MESSAGE, bytesRead) == 0) */
      }  /* if ((bytesRead = recvfrom (udpSocket.... */
      else
      {
        /* Read zero or negative length */
        if (bytesRead == 0)
          fprintf (TCP_ErrorServerOutput, "Received zero byte message from %s:%i - %s\n", inet_ntoa (address.sin_addr), ntohs (address.sin_port), get_socket_error());
        else
          fprintf (TCP_ErrorServerOutput, "Received error message from %s:%i - %s\n", inet_ntoa (address.sin_addr), ntohs (address.sin_port), get_socket_error());
      }
    }  /* if (FD_ISSET (udpSocket, &socketDescriptors)) */

    /*---------------------------------------------------------------------*/
    /* Go through the list of TCP clients and deal with any I/O operations */
    /*---------------------------------------------------------------------*/
    for (i = 0; i < tcpSocketList.numberOfClients; i++)
    {
      socketDescriptor = tcpSocketList.socketID[i];

      if (FD_ISSET (socketDescriptor, &socketDescriptors))
      {
        /* Check if the socket was closed, or if data was written */
        /* if ((bytesRead = read (socketDescriptor, RxBuffer, BUFFER_SIZE)) == 0) */
        if ((bytesRead = recv (socketDescriptor, (char*) RxBuffer, BUFFER_SIZE, 0)) <= 0)
        {
          /* Socket was closed or is broken */
          if (bytesRead < 0)
            fprintf (TCP_ErrorServerOutput, "Error reading data on socket descriptor %i, IP address %s: %s\n", socketDescriptor, tcpSocketList.ipAddress[i], get_socket_error());
          
          /* send the HW_Close() message */
          RxBuffer[0] = CODE_HW_Close_Internal;
          bytesRead = 1;
          relayMessage (i, &tcpSocketList, RxBuffer, &bytesRead, TxBuffer, &TxBufferSize);
          
          if (TCP_VERBOSE)
            fprintf (TCP_VerboseServerOutput, "Client disconnected: Socket descriptor is %i, IP address %s\n", socketDescriptor, tcpSocketList.ipAddress[i]);
          CLOSE (socketDescriptor);

          /* delete this socketDescriptor and associated names and IP addresses from the list and shift everything down*/
          
          /* first store the deleted strings to move to the end of the list and zero them */
          ipAddressPointer = tcpSocketList.ipAddress[i];
          ipAddressPointer[0] = 0;
          hostNamePointer = tcpSocketList.hostName[i];
          hostNamePointer[0] = 0;
          
          for (j = i; j < tcpSocketList.numberOfClients-1; j++)
          {
            tcpSocketList.socketID[j] = tcpSocketList.socketID[j+1];
            tcpSocketList.ipAddress[j] = tcpSocketList.ipAddress[j+1];
            tcpSocketList.hostName[j] = tcpSocketList.hostName[j+1];
          }
            
          /* zero fill the last socket ID in the list */
          tcpSocketList.socketID[j] = 0;
          tcpSocketList.ipAddress[j] = ipAddressPointer;
          tcpSocketList.hostName[j] = hostNamePointer;

          tcpSocketList.numberOfClients--;

          /* check if we have to go back through the list to find the largestSocketDescriptor */
          if (largestSocketDescriptor == socketDescriptor)
          {
            largestSocketDescriptor = tcpSocketList.socketID[0];
            for (j = 1; j < tcpSocketList.numberOfClients; j++)
              if (tcpSocketList.socketID[j] > largestSocketDescriptor) largestSocketDescriptor = tcpSocketList.socketID[j];

            /* check the master and UDP sockets */
            if (masterSocket > largestSocketDescriptor) largestSocketDescriptor = masterSocket;
            if (udpSocket > largestSocketDescriptor) largestSocketDescriptor = udpSocket;
          }
        }
        else
        {
          /* we have incoming data */
          if (TCP_VERBOSE)
          {
            fprintf (TCP_VerboseServerOutput, "%i byte message from socket descriptor %i, index #%i, IP address %s:\n", bytesRead, socketDescriptor, i, tcpSocketList.ipAddress[i]);

            for (j = 0; j < bytesRead; j++) fprintf (TCP_VerboseServerOutput, "%.2X ", RxBuffer[j]);
            fprintf (TCP_VerboseServerOutput, "\n");
          }  /* if (TCP_VERBOSE) */

          /* relay the message to the local hwInterface */
          relayMessage (i, &tcpSocketList, RxBuffer, &bytesRead, TxBuffer, &TxBufferSize);
          if (send (socketDescriptor, (char*) TxBuffer, TxBufferSize, 0) < 0)
            fprintf (TCP_ErrorServerOutput, "Error writing data on socket descriptor %i, IP address %s:%i - %s\n",
                     socketDescriptor, inet_ntoa (address.sin_addr), ntohs (address.sin_port), get_socket_error());
        }
      }  /* for (i = 0; i < tcpSocketList.numberOfClients; i++) */
    }

    /*---------------------------------------------------------------------*/
    /* Check for new client connection requests on the master socket       */
    /*---------------------------------------------------------------------*/ 
    if (FD_ISSET (masterSocket, &socketDescriptors))
    {
      /* check if we have room for another client */
      if ((newClientSocket = accept (masterSocket, (struct sockaddr*) &address, &addressLength)) == INVALID_SOCKET)
        fprintf (TCP_ErrorServerOutput, "Error on accepting new client socket: %s\n", get_socket_error());

      if (tcpSocketList.numberOfClients < MAX_NUMBER_OF_CLIENTS)
      {
        if (setKeepAliveOptions(newClientSocket) != 0)
        {
          if (TCP_VERBOSE)
            fprintf (TCP_VerboseServerOutput, "Rejected new connection: Socket descriptor is %i, IP address %s:%i as set socket options failed\n",
                     newClientSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
          CLOSE (newClientSocket);
        }
        else
        {
          /* add new socket and IP address to list of sockets - hostname will come later from the Identify message */
          sprintf (tcpSocketList.ipAddress[tcpSocketList.numberOfClients], "%s:%i", inet_ntoa(address.sin_addr), ntohs (address.sin_port));
          tcpSocketList.socketID[tcpSocketList.numberOfClients++] = newClientSocket;
          
          /* print socket details - used in send and receive commands */
          if (TCP_VERBOSE)
            fprintf (TCP_VerboseServerOutput, "Accepted new connection: Socket descriptor is %i, index is %i, IP address %s\n", newClientSocket, tcpSocketList.numberOfClients - 1, tcpSocketList.ipAddress[tcpSocketList.numberOfClients - 1]);
          if (newClientSocket > largestSocketDescriptor) largestSocketDescriptor = newClientSocket;
        }
      }
      else
      {
        /* Number of allowed connections exceeded - print socket details - and close */
        if (TCP_VERBOSE)
          fprintf (TCP_VerboseServerOutput, "Rejected new connection: Socket descriptor is %i, IP address %s:%i as number of connections has been exceeded\n",
                   newClientSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
        CLOSE (newClientSocket);
      }
    }  /* if (FD_ISSET (masterSocket, &socketDescriptors)) */
  }

  CLOSE (masterSocket);
  CLOSE (udpSocket);
  return stopServer(TCP_SERVER_E_OK);
}

#ifdef __cplusplus
}
#endif

/*@}*/
